/*
 * Copyright (c) 2019 Of Him Code Technology Studio
 * Jpom is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 * 			http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package com.lx.util;

import com.amazonaws.util.Base32;
import com.lx.util.secure.algorithm.SecureAlgorithm;
import com.lx.util.secure.algorithm.summary.HmacSha;
import org.springframework.util.Assert;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author bwcx_jzy
 * @since 2022/1/27
 */
public class TwoFactorAuthUtils {

    public static void main(String[]args){
//        String tfaKey = TwoFactorAuthUtils.generateTFAKey();
//        String s = generateOtpAuthUrl("test", tfaKey);
//        System.out.println(s);

        String tfaKey = "HMHPPQEQHQKTJZUIZJQISSXUCJGKRITH";
        String tfaCode = TwoFactorAuthUtils.generateTFACode(tfaKey);
        System.out.println(tfaCode);
//        String tfaCode = "520173";
        boolean validateTFACode = TwoFactorAuthUtils.validateTFACode(tfaKey, tfaCode);
        System.out.println(validateTFACode+" "+tfaKey + "  " + tfaCode + "  " + Thread.currentThread().getName());
        Assert.state(validateTFACode, "验证失败:" + tfaCode);
    }

    /** 说明: 验证MAF code是否正确 默认60s内有效
     * @author ylx 2025/8/8 10:05 */
    public static boolean validateTFACode(String tfaKey, String tfaCode) {
        return validateTFACode(tfaKey,tfaCode,60, TimeUnit.SECONDS);
    }
    /** 说明: 验证MAFcode是否正确 可以指定验证码有效时间
     * @author ylx 2025/8/8 9:59 */
	public static boolean validateTFACode(String tfaKey, String tfaCode, int duration , TimeUnit timeUnit) {
		int validCode = Integer.parseInt(tfaCode);
		return TimeBasedOneTimePasswordUtil.validateCurrentNumber(tfaKey, validCode, (int) timeUnit.toMillis(duration));
	}

    /** 说明: 根据用户名和秘钥生成二维码链接  阿里云MFA 谷歌身份验证器  腾讯身份验证码
     * @author ylx 2025/8/8 9:56 */
	public static String generateOtpAuthUrl(String userName, final String tfaKey) {
		return TimeBasedOneTimePasswordUtil.generateOtpAuthUrl(userName, tfaKey);
	}

    /** 说明: 生成32位随机秘钥
     * @author ylx 2025/8/8 9:55 */
    public static String generateTFAKey() {
        return TimeBasedOneTimePasswordUtil.generateAlphaUUID();
    }

    /** 说明: 根据秘钥生成30秒随机code
     * @author ylx 2025/8/8 9:50 */
    public static String generateTFACode(String tfaKey) {
        return TimeBasedOneTimePasswordUtil.generateCurrentNumberString(tfaKey);
    }



	private static class TimeBasedOneTimePasswordUtil {
		public static final int DEFAULT_TIME_STEP_SECONDS = 30;
		private static final int NUM_DIGITS_OUTPUT = 6;

        private static final String ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private static final Random random = new Random();
        public static String generateAlphaUUID() {
            StringBuilder sb = new StringBuilder(32);
            for (int i = 0; i < 32; i++) {
                // 随机选取字母
                sb.append(ALPHA.charAt(random.nextInt(ALPHA.length())));
            }
            return sb.toString();
        }

		public static boolean validateCurrentNumber(String base32Secret, int authNumber, int windowMillis) {
			return validateCurrentNumber(base32Secret, authNumber, windowMillis, System.currentTimeMillis(), DEFAULT_TIME_STEP_SECONDS);
		}

		public static boolean validateCurrentNumber(String base32Secret, int authNumber,
													int windowMillis, long timeMillis,
													int timeStepSeconds) {
			long fromTimeMillis = timeMillis;
			long toTimeMillis = timeMillis;
			if (windowMillis > 0) {
				fromTimeMillis -= windowMillis;
				toTimeMillis += windowMillis;
			}
			long timeStepMillis = timeStepSeconds * 1000L;
			for (long millis = fromTimeMillis; millis <= toTimeMillis; millis += timeStepMillis) {
				int generatedNumber = generateNumber(base32Secret, millis, timeStepSeconds);
				if (generatedNumber == authNumber) {
					return true;
				}
			}
			return false;
		}

		public static String generateCurrentNumberString(String base32Secret) {
			return generateNumberString(base32Secret, System.currentTimeMillis(), DEFAULT_TIME_STEP_SECONDS);
		}

		public static String generateNumberString(String base32Secret, long timeMillis, int timeStepSeconds) {
			int number = generateNumber(base32Secret, timeMillis, timeStepSeconds);
			String numStr = Integer.toString(number);
            return LX.left(numStr,6,'0');
		}

		public static int generateNumber(String base32Secret, long timeMillis, int timeStepSeconds) {
			byte[] key = Base32.decode(base32Secret);
			byte[] data = new byte[8];
			long value = timeMillis / 1000 / timeStepSeconds;
			for (int i = 7; value > 0; i--) {
				data[i] = (byte) (value & 0xFF);
				value >>= 8;
			}
            SecureAlgorithm hmacSha = new HmacSha();
            byte[] hash = new HmacSha().encryptAlgorithm("HmacSHA1").key(key).encrypt(data);
			// take the 4 least significant bits from the encrypted string as an offset
			int offset = hash[hash.length - 1] & 0xF;
			// We're using a long because Java hasn't got unsigned int.
			long truncatedHash = 0;
			for (int i = offset; i < offset + 4; ++i) {
				truncatedHash <<= 8;
				// get the 4 bytes at the offset
				truncatedHash |= hash[i] & 0xFF;
			}
			// cut off the top bit
			truncatedHash &= 0x7FFFFFFF;

			// the token is then the last 6 digits in the number
			truncatedHash %= 1000000;
			// this is only 6 digits so we can safely case it
			return (int) truncatedHash;
		}

		public static String generateOtpAuthUrl(String keyId, String secret) {
			//			addOtpAuthPart(keyId, secret, sb);
			return "otpauth://totp/" + keyId + "?secret=" + secret;
		}
	}
}
